#include "miniwin.h"

#define MINI_GNU_VERS "0.9"
static char *s_vers; 

#ifndef _WIN64
 static BOOL is_wow = false;
 static BOOL (WINAPI *s_revert)(PVOID) = NULL;
 static PVOID s_prevert;
#endif

const char *Win::vers(const char *prog)
{
   alloc((void* &)s_vers, 64);
   wsprintfA(s_vers, "=(W17)=  %s mini-gnu tools  %s%s  version %s\r\n",
      prog,
#ifdef _WIN64
# ifdef _M_IA64
      "win-ia64", "",
# else
      "win-x64", "",
# endif
#else
      "win-x86", is_wow ? "/wow64" : "",
#endif
      MINI_GNU_VERS
   );
   return s_vers;
}

static const char
#ifndef _WIN64
   dis_c[] = "Wow64DisableWow64FsRedirection",
   rev_c[] = "Wow64RevertWow64FsRedirection",
   wow_c[] = "IsWow64Process",
#endif
   ntd_c[] = "ntdll",
   krn_c[] = "kernel32",
   adv_c[] = "advapi32",
   rpc_c[] = "rpcrt4";

HMODULE Win::hnt, Win::hkern, Win::hadv, Win::hrpc;
int Win::os_version;

void Win::init()
{
   OSVERSIONINFOA oi; Win::bzero(&oi, sizeof(oi));
   oi.dwOSVersionInfoSize = (DWORD)sizeof(OSVERSIONINFOA);
   if (!::GetVersionExA((LPOSVERSIONINFOA)&oi) || oi.dwPlatformId < VER_PLATFORM_WIN32_NT)
   { panic("Win 9x is not supported", 94); }
   os_version = (int)(0x100 * oi.dwMajorVersion + oi.dwMinorVersion);

   hnt   = ::GetModuleHandleA( ntd_c );
   hkern = ::GetModuleHandleA( krn_c );
   hadv  = ::GetModuleHandleA( adv_c );
   hrpc  = ::GetModuleHandleA( rpc_c );

#ifndef _WIN64
   BOOL (WINAPI *IsWow)(HANDLE,PBOOL);
   BOOL (WINAPI *disable)(PVOID*);
   if (NULL != hkern
    && NULL != (*(FARPROC*)&disable  = ::GetProcAddress(hkern, dis_c))
    && NULL != (*(FARPROC*)&s_revert = ::GetProcAddress(hkern, rev_c))
    && NULL != (*(FARPROC*)&IsWow    = ::GetProcAddress(hkern, wow_c))
    && (*IsWow)(::GetCurrentProcess(), &is_wow)
    && is_wow
   )
   (*disable)(&s_prevert);
#endif
}

void Win::panic( const char *errstr, int exit_code )
{
   HANDLE fhe = ::GetStdHandle(STD_ERROR_HANDLE);
   DWORD dw;
   bool console = TRUE == ::GetConsoleMode(fhe, &dw);

   char ss[512];
   ::lstrcpyA(ss, "panic: ");
   ::lstrcatA(ss, errstr);
   ::lstrcatA(ss, "\r\n");
   dw = (DWORD)::lstrlenA(ss);

   if ( console ) ::WriteConsoleA(fhe, ss, dw, &dw, NULL);
   else ::WriteFile(fhe, ss, dw, &dw, NULL);

   ::ExitProcess((UINT)exit_code);
}

static void nomem_panic()
{
   ::SetLastError(ERROR_NOT_ENOUGH_MEMORY);
   Win::panic("not enough memory", 90);
}

static PVOID *blocks = NULL;
static int mblocks = 1024, nblocks = 0;

void* Win::alloc( int size )
{
   SIZE_T sz = (sizeof(PVOID)) * mblocks;
   if ( !blocks ) {
      blocks = (PVOID *)::HeapAlloc(
         ::GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS | HEAP_ZERO_MEMORY, sz
      );
   }
   if ( nblocks == mblocks ) {
      sz *= 2; mblocks *= 2;
      blocks = (PVOID *)::HeapReAlloc(
         ::GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS | HEAP_ZERO_MEMORY, blocks, sz
      );
   }
   if (!blocks) nomem_panic();

   PVOID pv = ::HeapAlloc(::GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, size);
   if ( pv ) blocks[nblocks++] = pv;
   return pv;
}

void Win::alloc( void* &lpbuff, int size )
{
   if ( NULL != lpbuff ) return;
   if (NULL == (lpbuff = alloc(size))) nomem_panic();
}

void Win::cleanup()
{
#ifndef _WIN64
   if (is_wow && s_revert) (*s_revert)(s_prevert);
#endif

   if ( !blocks )
   { return; }

   while (nblocks > 0)
   { ::HeapFree(::GetProcessHeap(), 0, blocks[--nblocks]); }

   ::HeapFree(::GetProcessHeap(), 0, blocks);
   blocks = NULL;
}

char *Win::strdup( const char *str )
{
   int nb = ::lstrlenA(str);
   char *ps = NULL; alloc((void* &)ps, nb+1);
   ::lstrcpyA(ps, str);
   return ps;
}

WCHAR *Win::strdup( const WCHAR *str )
{
   int nb = 2 * ::lstrlenW(str);
   WCHAR *pw = NULL; alloc((void* &)pw, nb+2);
   ::lstrcpyW(pw, str);
   return pw;
}

bool Win::geterr( WCHAR *text, int msize )
{
   DWORD dwo, dwe = ::GetLastError();

   text[0] = text[msize-1] = L'\0';
   if ( ERROR_SUCCESS == dwe )
      return false;
 
   dwo = ::FormatMessageW(
      FORMAT_MESSAGE_IGNORE_INSERTS|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_MAX_WIDTH_MASK,
      NULL, dwe, 0, text, (DWORD)msize-1, NULL
   );
   
   return (dwo > 0);
}

bool Win::exists( const WCHAR *name, DWORD attr )
{
   DWORD dw = ::GetFileAttributesW( name );
   if ( INVALID_FILE_ATTRIBUTES == dw )
      return false;

   return ( dw & attr ) == attr;
}

int Win::wtoa( const WCHAR *src, char *dst, int msize, UINT cp )
{ return ::WideCharToMultiByte(cp, 0, src, -1, dst, msize, NULL,NULL); }

int Win::wtoa( const WCHAR *src, int nw, char *dst, int msize, UINT cp )
{ return ::WideCharToMultiByte(cp, 0, src, nw, dst, msize, NULL,NULL); }

int Win::atow( const char *src, WCHAR *dst, int msize, UINT cp )
{ return ::MultiByteToWideChar(cp, 0, src, -1, dst, msize); }

int Win::atow( const char *src, int na, WCHAR *dst, int msize, UINT cp )
{ return ::MultiByteToWideChar(cp, 0, src, na, dst, msize); }

void Win::bzero( void *pbuf, size_t nb )
{
   BYTE *pb = (BYTE *)pbuf, *pe = (BYTE *)pbuf + nb - 1;
   while ( pe >= pb ) { *pb++ = *pe-- = 0; } // avoid _memset optimization
}

void Win::bset( void *pbuf, int v, size_t nb )
{
   BYTE *pb = (BYTE *)pbuf, *pe = (BYTE *)pbuf + nb - 1;
   while ( pe >= pb ) { *pb++ = *pe-- = (BYTE)v; } // avoid _memset optimization
}

int Win::bcmp( const void *pbuf1, const void *pbuf2, size_t nb )
{
   BYTE *pb1 = (BYTE *)pbuf1, *pb2 = (BYTE *)pbuf2;
   while ( nb-- > 0 )
   {
      if ( *pb1 != *pb2 ) return (int)*pb1 - (int)*pb2;
      ++pb1; ++pb2;
   }
   return 0;
}

void Win::bmov( void *dest, const void *source, size_t nb )
{
   BYTE *pd = (BYTE *)dest, *ps = (BYTE *)source;
   if ( nb <= 0 || pd == ps) return;
   int dir = +1;
   if ( pd > ps ) { dir = -1; pd += nb - 1; ps += nb - 1; }
   while ( --nb > 0 ) { *pd = *ps; pd += dir; ps += dir; }
   *pd = *ps;
}

void Win::bswap( WCHAR *dst, int nw, const WCHAR *src )
{
   if (!src) src = (const WCHAR *)dst;
   while ( --nw >= 0 )
   { unsigned t = (unsigned)*src++; *dst++ = (WCHAR)((t >> 8) | (t << 8)); }
}

//=============================================================================

void Win::init_search_context( SearchContext *ctx,
   WCHAR *pw, int cb, CheckFunc pf, const void *pd1, const void *pd2 )
{
      ctx->path = pw;
      ctx->cb_path = cb; ctx->nb_path = 0;
      ctx->check_one = pf;
      ctx->p1.pv = pd1;
      ctx->p2.pv = pd2;
      ctx->found = 0;
      ctx->done = false;
      ctx->ignore = false;
}

#ifndef FIND_FIRST_EX_LARGE_FETCH
#define FIND_FIRST_EX_LARGE_FETCH      0x00000002
#endif

int Win::search_files(
   SearchContext *ctx, const WCHAR *base, const WCHAR *mask, bool recursive )
{
   int nf = 0, n0 = ctx->nb_path;
   int nb = ::lstrlenW(base); if (nb > 0 && base[nb-1] == L'\\') --nb;
   int nm = ::lstrlenW(mask);
   if ( ctx->cb_path <= n0 + nb + 1 + nm) // too long name
      return nf;

   ::lstrcpyW(ctx->path + n0, base);
   ctx->path[n0 + nb] = L'\\';
   ::lstrcpyW(ctx->path + (ctx->nb_path = n0 + nb + 1), mask);
   WIN32_FIND_DATAW ff;

   HANDLE hf = ::FindFirstFileEx(
      ctx->path, FindExInfoStandard, &ff, FindExSearchNameMatch, NULL, 
      os_version < 0x601 ? 0 : FIND_FIRST_EX_LARGE_FETCH
   );
   if (INVALID_HANDLE_VALUE != hf) {
      BOOL res;         
      do {
         bool is_dot = *ff.cFileName == L'.' &&
           (!ff.cFileName[1] || (ff.cFileName[1] == L'.' && !ff.cFileName[2]));
         if (!is_dot) {
            if ((*ctx->check_one)(&ff, ctx)) ++nf;
            if ( ctx->done )
               break;
            if (!ctx->ignore) {
               if ( recursive && 0 != (ff.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) )
               {
                  nf += search_files(ctx, ff.cFileName, mask, true);
                  if ( ctx->done )
                     break;
               }
            }
         }
         res = !ctx->done ? ::FindNextFileW(hf, &ff) : FALSE;
      } while( res );
      ::FindClose(hf);
   }

   ctx->nb_path = n0;
   return nf;
}
